package aword;

import java.io.BufferedInputStream;
import java.io.BufferedOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.OutputStream;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;

import kameleon.document.Array;
import kameleon.document.Cell;
import kameleon.document.CellHeader;
import kameleon.document.Document;
import kameleon.document.ElementPropertiesDefaultNames;
import kameleon.document.Paragraph;
import kameleon.document.Paragraphs;
import kameleon.document.Row;
import kameleon.document.Text;
import kameleon.document.TextParagraph;
import kameleon.document.TextParagraphElement;
import kameleon.document.Title;
import kameleon.exception.InvalidIndexException;
import kameleon.exception.InvalidPropertyException;
import kameleon.exception.KameleonException;
import kameleon.plugin.Analyzer;
import kameleon.plugin.SupportedOptions;
import kameleon.util.IODocument;
import kameleon.util.IOFile;
import aword.analyze.LexicalAnalyzer;
import aword.analyze.SyntaxAnalyzer;

/**
 * Main class.
 * 
 * <p>Launches the analyzer for the given file and writes the result to the given file.
 * 
 * <p>Proper usage requires three command line arguments : 
 * <pre>  -a &lt;analyzed file> &lt;result file></pre>
 * 
 * @author		Dervin Cyrielle, Schnell Michaël
 * @version		1.0
 */
public class Main extends Analyzer {

	/**
	 * Main function.
	 * 
	 * @param 	args
	 * 			command line arguments
	 * 
	 * @see		Main#checkArgs(String[])
	 */
	public static void main(String[] args) {
		//		new Main(args) ;
		try {
			new Main().copyPicture(
					"C:\\Users\\Kay\\Desktop\\.kameleon\\.resources") ;
		} catch (KameleonException e) {
			//			e.printStackTrace();
		}
	}// main

	/**
	 * 
	 */
	public Main() {
		super() ;
	}

	/**
	 * Sole constructor.
	 * 
	 * @param 	args
	 * 			command line arguments
	 * 
	 * @see		Main#checkArgs(String[])
	 */
	public Main(String[] args) {
		this() ;
		try {
			/* Check and retrieve arguments */
			Main.checkArgs(args) ;
			String analyzedFile = args[1] ;
			String resultFile   = args[2] ;

			/* Analyze file */
			System.out.printf("~~ Word Analyzer ~~\n") ;
			System.out.printf("Analyzing file '%s'.\n",
					analyzedFile) ;
			Document d = Main.analyzeFile(analyzedFile) ;

			/* Correct analyze flaws */
			Main.refactorTitles(d) ;
			Main.refactorParagraphs(d) ;
			Main.refactorTables(d) ; 

			/* Save result */
			System.out.print("Saving result ....\n") ;
			IODocument.writeToFile(d, resultFile) ;
			System.out.print("File successfully analyzed.\n") ;
		} catch (KameleonException e) {
			System.exit(1) ;
		}// try
	}// Main(String[])

	/**
	 * Checks the given command line arguments.
	 * 
	 * <p>The program needs 3 arguments to run properly :
	 * <ol>
	 * 	<li>{@code -a} : analyze the given file
	 * 	<li>{@code <analyzed file>} : path of the file analyzed by the program
	 * 	<li>{@code <result file>} : path of the target file for the result
	 * 
	 * @param 	args
	 * 			command line arguments
	 * 
	 * @throws	KameleonException
	 * 			If the number of arugments is incorrect
	 */
	protected static void checkArgs(String[] args) throws KameleonException {
		int nExpectedParamters = 4 ;
		if (args.length != nExpectedParamters) {
			throw new KameleonException(//TODO Translate error message
					String.format("Nombre d'arguments incorrect, %d trouves, %d attendus.\n",
							args.length, nExpectedParamters)) ;
		}// if
	}// checkArgs(String[])

	/**
	 * Analyzes the given file.
	 * 
	 * @param 	analyzedFile
	 * 			path of the analyzed file
	 * 
	 * @return	instance of {@code Document} resulting from the analyzed file
	 * 
	 * @throws 	KameleonException
	 * 			if an error occurred while analyzing the file
	 */
	protected static Document analyzeFile(String analyzedFile) 
			throws KameleonException {
		try {
			SyntaxAnalyzer a = new SyntaxAnalyzer(
					new LexicalAnalyzer(new InputStreamReader(
							new FileInputStream(analyzedFile), "UTF-8"))) ;
			return (Document) a.parse().value ;
		} catch (FileNotFoundException e) {
			//TODO Ajouter notre exception
			throw new KameleonException(e.getMessage()) ;
		} catch (Exception e) {
			//TODO Ajouter notre exception
			throw new KameleonException(e.getMessage()) ;
		}// try
	}// analyzeFile(String)

	protected static void refactorTitles(Document d) 
			throws KameleonException {
		int index = 0 ;
		HashMap<Integer, Paragraph> replacement = new HashMap<Integer, Paragraph>() ;
		for(Paragraph p : d) {
			if (p instanceof TextParagraph) {
				TextParagraph tp = (TextParagraph) p ;
				if (p.isProperty(ElementPropertiesDefaultNames.STYLE)) {
					String style = (String) p.getProperty(ElementPropertiesDefaultNames.STYLE) ;
					int titleLevel = Main.getTitleLevel(style) ;
					if (titleLevel > 0) {
						Title title = new Title() ;
						title.setProperty(ElementPropertiesDefaultNames.TEXT_ALIGNMENT, 
								title.getProperty(ElementPropertiesDefaultNames.TEXT_ALIGNMENT)) ;
						title.setProperty(ElementPropertiesDefaultNames.TITLE_LEVEL, 
								titleLevel) ;
						title.setProperty(ElementPropertiesDefaultNames.TEXT_BODY, 
								Main.getTextBody(tp)) ;
						replacement.put(index, title) ;
						//					System.out.printf("Ajout d'un titre à l'indice %d, contenu '%s'\n",
						//							index, title.getProperty(ElementPropertiesDefaultNames.TEXT_BODY)) ;
					}// if
				}// if
			}// if
			index++ ;
		}// for
		for(Integer pos : replacement.keySet()) {
			d.remove(pos) ;
			if (pos == d.getCount()) {
				d.append(replacement.get(pos)) ;
			} else {
				d.add(replacement.get(pos), pos) ;
			}// if
		}// for
	}// refactorTitles(Document)

	protected static void refactorParagraphs(Document d) {
		refactorParagraphs(d.getParagraphs()) ;
	}// removeEmptyParagraphs(Document)

	protected static void refactorParagraphs(Paragraphs paragraphs) {
		for(Iterator<Paragraph> iter=paragraphs.iterator(); iter.hasNext(); ) {
			Paragraph p = iter.next() ;
			if (p instanceof TextParagraph) {
				TextParagraph tp = (TextParagraph) p ;
				if (tp.getCount() == 0) {
					iter.remove() ;
				} else {
					fuseTextParts(tp) ;
				}// if
			} else if (p instanceof Array) {
				Array array = (Array) p ;
				for(Row row : array) {
					for(Cell cell : row) {
						refactorParagraphs(cell.getParagraphs()) ;
					}// for
				}// for
			}// if
		}// for
	}// removeEmptyParagraphs(Document)
	
	protected static void fuseTextParts(TextParagraph tp) {
		Iterator<TextParagraphElement> iter = tp.iterator() ;
		TextParagraphElement nextElement ;
		List<Integer> removed = new ArrayList<Integer>() ;
		int currentIndex = -1 ;// Position of nextElement
		while (iter.hasNext()) {
			nextElement = iter.next() ;
			currentIndex++ ;
			if (nextElement instanceof Text) {
				while (fuseText((Text) nextElement, currentIndex, iter, removed)) {
					currentIndex++ ;
				}// while
				currentIndex++ ;
			}// if
		}// while
		for (Integer removedIndex : removed) {
			System.out.printf("Removing index: %d\n", removedIndex);
			try {
				tp.remove(removedIndex.intValue()) ;
			} catch (InvalidIndexException e) {
				// TODO Auto-generated catch block
				// This hsould not happen !
			}// try
		}// for
	}// fuseTextParts(TextParagraph)
	
	private static boolean fuseText(Text current, int currentIndex,
			Iterator<TextParagraphElement> following, List<Integer> removed) {
		boolean fused = false ;
		if (following.hasNext()) {
			TextParagraphElement next = following.next() ;
			currentIndex++ ;
			if (next instanceof Text) {
				Text nextText = (Text) next ;
				if (equals(current, nextText)) {
					try {
						current.setProperty(ElementPropertiesDefaultNames.TEXT_BODY,
								String.format("%s%s", 
										current.getProperty(ElementPropertiesDefaultNames.TEXT_BODY), 
										nextText.getProperty(ElementPropertiesDefaultNames.TEXT_BODY))) ;
						if (removed.isEmpty()) {
							removed.add(new Integer(currentIndex)) ;
						} else {
							removed.add(0, new Integer(currentIndex)) ;
						}
						fused = true ;
					} catch (InvalidPropertyException e) {
						// TODO: handle exception
						// This hsould not happen !
					}// try
				}// if
			}// if
		}// if
		return fused ;
	}// fuseText(Text current, Iterator<TextParagraphElement>)
	
	private static boolean equals(Text a, Text b) {
		boolean equal = true ;
		String[] params = new String[]{
				ElementPropertiesDefaultNames.FORMAT_BOLD,
				ElementPropertiesDefaultNames.FORMAT_ITALIC,
				ElementPropertiesDefaultNames.FORMAT_MONOSPACE,
				ElementPropertiesDefaultNames.FORMAT_STRUCK,
				ElementPropertiesDefaultNames.FORMAT_SUBSCRIPT,
				ElementPropertiesDefaultNames.FORMAT_SUPERSCRIPT,
				ElementPropertiesDefaultNames.FORMAT_UNDERLINED
		} ;
		
		try {

			System.out.printf("Comparing ...\n'%s'\n%s\n\twith\n'%s'\n%s\n",
					a.getProperty(ElementPropertiesDefaultNames.TEXT_BODY), a,
					b.getProperty(ElementPropertiesDefaultNames.TEXT_BODY), b) ;
		} catch (InvalidPropertyException e) {
			/* Will not happen ! */
		}// try
		
		int p = 0, maxP = params.length ;
		while (equal && (p < maxP)) {
			String key = params[p] ;
			try {
				equal = (a.isProperty(key) && a.getProperty(key).equals(b.getProperty(key)))
						|| (!a.isProperty(key) && !b.isProperty(key)) ;
			} catch (InvalidPropertyException e) {
				/* Will not happen ! */
			}// try
			++p ;
		}// while
		
		System.out.printf("Result: %b\n", equal) ;
		return equal ;
	}// equals(Text, Text)

	protected static int getTitleLevel(String style) {
		final String[] titleStyles = new String[]{
				"Titre1", "Titre2", "Titre3", "Titre4", "Titre5"
		} ;
		int index = Arrays.binarySearch(titleStyles, style) ;
		if (index >= 0) {
			index++ ;
		}// if
		return index ;
	}// getTitleLevel(String)

	protected static String getTextBody(TextParagraph p) {
		StringBuilder text = new StringBuilder() ;
		for(TextParagraphElement el : p) {
			try {
				text.append(el.getProperty(ElementPropertiesDefaultNames.TEXT_BODY)) ;
			} catch (InvalidPropertyException e) {
				/* This should not happen. */
			}// try
		}// for
		return text.toString() ;
	}// getTextBody(TextParagraph)

	protected static void refactorTables(Document d) {
		for(Paragraph p : d) {
			if (p instanceof Array) {
				Array array = (Array) p ;
				if (array.getCount() <= 0) {
					continue ;
				}// if
				Row firstRow = array.iterator().next() ;
				boolean isHeaderRow = true ;
				for(Iterator<Cell> iter = firstRow.iterator(); iter.hasNext() && isHeaderRow; ) {
					Cell cell = iter.next() ;
					for(Iterator<Paragraph> cellIter = cell.iterator(); cellIter.hasNext() && isHeaderRow; ) {
						Paragraph paragraph = cellIter.next() ;
						if (paragraph instanceof TextParagraph) {
							TextParagraph tp = (TextParagraph) paragraph ;
							for(Iterator<TextParagraphElement> tpIter = tp.iterator(); tpIter.hasNext() && isHeaderRow; ) {
								TextParagraphElement element = tpIter.next() ;
								try {
									isHeaderRow = Boolean.TRUE.equals(element.getProperty(ElementPropertiesDefaultNames.FORMAT_BOLD)) ;
								} catch (InvalidPropertyException e) {
									/* This should not happen. */
								}// try
							}// for
						} else {
							try {
								isHeaderRow = Boolean.TRUE.equals(p.getProperty(ElementPropertiesDefaultNames.FORMAT_BOLD)) ;
							} catch (InvalidPropertyException e) {
								/* This should not happen. */
							}// try
						}// if
					}// for
				}// for
				if (isHeaderRow) {
					int nCells = firstRow.getCount() ;
					for(int cellIndex=0; cellIndex < nCells; ++cellIndex) {

						CellHeader header = new CellHeader(firstRow.getCell(cellIndex)) ;
						try {
							firstRow.add(header, cellIndex) ;
							firstRow.remove(cellIndex+1) ;
						} catch (KameleonException e) {/* Never called. */}
					}// for
				}// if
			}// if
		}// for
	}// refactorTables(Document)

	protected static void printDocumentContent(Document d) {
		System.out.printf("Document : %d elements", d.getCount()) ;
		for(Paragraph p : d) {
			System.out.printf("\t- %s\n", p.getClass()) ;
		}// for
	}// printDocumentContent(Document)

	/**
	 *
	 */
	@Override
	public Document analyze(String filePath, SupportedOptions options) throws KameleonException {
		/* Analyze file */
		Document d = Main.analyzeFile(filePath) ;

		/* Correct analyze flaws */
		Main.refactorTitles(d) ;
		Main.refactorParagraphs(d) ;
		Main.refactorTables(d) ; 

		return d ;
	}// analyze(String filePath, SupportedOptions)

	@Override
	public Document analyze(String filePath) throws KameleonException {
		return this.analyze(filePath, this.getDefaultOptions()) ;
	}

	@Override
	public void copyPicture(String targetFolder) throws KameleonException {
		String[] pictureNames = new String[]{
				"WordML2003Analyzer_mini.png",
				"WordML2003Analyzer_gray_icon.png",
				"WordML2003Analyzer_icon.png"
		} ;
		for(String pictureName : pictureNames) {
			try {
				InputStream src = new BufferedInputStream(
						this.getClass().getResourceAsStream(
								String.format("/aword/ressources/%s", pictureName))) ; //$NON-NLS-1$
				OutputStream dest = new BufferedOutputStream(new FileOutputStream(String.format("%s%s%s", 
						targetFolder, File.separator, pictureName))) ;
				IOFile.copyFile(src, dest) ;
				src.close() ;
				dest.close() ;
			} catch (FileNotFoundException e) {
				//TODO Handle exception
				throw new KameleonException(e.getMessage()) ;
			} catch (IOException e) {
				throw new KameleonException(e.getMessage()) ;
			}// try
		}// for
	}

	protected SupportedOptions getDefaultOptions() {
		return null ;
	}

}// class Main
